package org.papiez.util.files;

import static java.util.concurrent.TimeUnit.MILLISECONDS;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.commons.lang.Validate;

public final class FileModificationWatcher {
	private static ScheduledExecutorService executor;

	private static final String THREAD_NAME = "file-modification-watcher";

	private static final int DEFAULT_TICK = 1000;

	private final File file;

	private final int tick;

	private final AtomicBoolean cancelled = new AtomicBoolean(false);

	private final Set<FileModificationListener> listeners = new CopyOnWriteArraySet<FileModificationListener>();

	public FileModificationWatcher(File file) throws FileNotFoundException {
		this(file, DEFAULT_TICK);
	}

	public FileModificationWatcher(File file, int tick)
			throws FileNotFoundException {

		Validate.notNull(file, "The file can't be null!");
		if (!file.isFile()) {
			throw new FileNotFoundException(file.getAbsolutePath());
		}
		this.file = file;

		Validate.isTrue(tick > 0, "The tick has to be greater than zero!");
		this.tick = tick;

		initializeExecutor();
		watch();
	}

	private static synchronized void initializeExecutor() {
		if (executor != null) {
			return;
		}

		ThreadFactory threadFactory = new ThreadFactory() {
			@Override
			public Thread newThread(Runnable runnable) {
				Thread thread = new Thread(runnable);

				thread.setName(THREAD_NAME);
				thread.setDaemon(true);
				thread.setPriority(Thread.MIN_PRIORITY);

				return thread;
			}
		};

		executor = Executors.newSingleThreadScheduledExecutor(threadFactory);
	}

	private void watch() {
		final long recentLastModified = file.lastModified();

		executor.schedule(new Runnable() {
			@Override
			public void run() {
				if (file.lastModified() > recentLastModified) {
					for (FileModificationListener listener : listeners) {
						listener.onFileModified(file);
					}
				}

				if (!cancelled.get()) {
					watch();
				}
			}
		}, tick, MILLISECONDS);
	}

	public void cancel() {
		cancelled.set(true);
	}

	public void addListener(FileModificationListener listener) {
		listeners.add(listener);
	}

	public void removeListener(FileModificationListener listener) {
		listeners.remove(listener);
	}
}
